package github.sf.fw.tools.container;


import github.sf.fw.tools.config.AppConfig;
import github.sf.fw.utils.ExceptionUtils;
import org.reflections.Reflections;

import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class ClassUtils {
    private static final ClassHelper CLASS_HELPER = new ClassHelper() {
        @Override
        protected Reflections getReflections(Class<?> clazz) {
            return new Reflections(AppConfig.getConfig(AppConfig.SCAN_PACKAGE), ClassUtils.class.getClassLoader());
        }
    };

    public static <T> T getInstance(Class<T> clazz) {
        return CLASS_HELPER.getInstance(clazz);
    }

    public static <T> T getInstanceOrWarn(Class<T> clazz) {
        return CLASS_HELPER.getInstanceOrWarn(clazz);
    }

    public static <T> T getInstanceOrDefault(Class<T> clazz, T defaultValue) {
        return CLASS_HELPER.getInstanceOrDefault(clazz, defaultValue);
    }

    public static <T> T getInstanceOrDefault(Class<T> clazz, Predicate<Class<? extends T>> filterOp, T defaultValue) {
        return CLASS_HELPER.getInstanceOrDefault(clazz, filterOp, defaultValue);
    }

    public static <T> List<T> getInstances(Class<T> clazz) {
        return CLASS_HELPER.getInstances(clazz);
    }

    public static <T> List<T> getInstances(Class<T> clazz, Predicate<Class<? extends T>> filterOp) {
        return CLASS_HELPER.getInstances(clazz, filterOp);
    }

    public static <T> List<Class<? extends T>> getClasses(Class<T> clazz) {
        return CLASS_HELPER.getClasses(clazz);
    }

    private static boolean isInterfaceOrAbstract(Class<?> clazz) {
        return clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers());
    }

    public abstract static class ClassHelper {

        protected abstract Reflections getReflections(Class<?> clazz);

        public <T> T getInstance(Class<T> clazz) {
            List<Class<? extends T>> factoryClasses = getReflections(clazz).getSubTypesOf(clazz)
                    .stream().filter(c -> !isInterfaceOrAbstract(c)).collect(Collectors.toList());
            if (factoryClasses.isEmpty()) {
                throw new ClassUtilsException(60053, "Cannot find instance for " + clazz.getSimpleName());
            } else if (factoryClasses.size() > 1) {
                throw new ClassUtilsException(60053, "Too many instances for " + clazz.getSimpleName() + ", exists: " + factoryClasses);
            }
            T t = null;
            try {
                t = factoryClasses.get(0).newInstance();
            } catch (Exception e) {
                throw new ClassUtilsException(60053, "Instance " + clazz.getSimpleName() + " failed", e);
            }
            return t;
        }

        public <T> T getInstanceOrWarn(Class<T> clazz) {
            return getInstance(clazz);
        }

        public <T> T getInstanceOrDefault(Class<T> clazz, T defaultValue) {
            Optional<T> optional = getReflections(clazz).getSubTypesOf(clazz)
                    .stream().filter(c -> !isInterfaceOrAbstract(c) &&
                            !c.isInstance(defaultValue)).findFirst().map(ExceptionUtils.map(Class::newInstance));
            return optional.orElse(defaultValue);
        }

        public <T> T getInstanceOrDefault(Class<T> clazz, Predicate<Class<? extends T>> filterOp, T defaultValue) {
            Optional<T> optional = getReflections(clazz).getSubTypesOf(clazz)
                    .stream().filter(c -> !isInterfaceOrAbstract(c) &&
                            filterOp.test(c)).findFirst().map(ExceptionUtils.map(Class::newInstance));
            return optional.orElse(defaultValue);
        }

        public <T> List<T> getInstances(Class<T> clazz) {
            return getInstances(clazz, c -> true);
        }

        public <T> List<T> getInstances(Class<T> clazz, Predicate<Class<? extends T>> filterOp) {
            return getReflections(clazz).getSubTypesOf(clazz)
                    .stream().filter(c -> !isInterfaceOrAbstract(c) && filterOp.test(c))
                    .map(ExceptionUtils.map(Class::newInstance)).collect(Collectors.toList());
        }

        public <T> List<Class<? extends T>> getClasses(Class<T> clazz) {
            return getReflections(clazz).getSubTypesOf(clazz)
                    .stream().filter(c -> !isInterfaceOrAbstract(c))
                    .collect(Collectors.toList());
        }
    }

}
